#include "gui.hh"

#include "downloader.hh"
#include "data_cache.hh"

#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Table_Row.H>
#include <FL/fl_draw.H>

#include <vector>
#include <string>
#include <ctime>

class MainWindow: public Fl_Double_Window {
public:
    using Fl_Double_Window::Fl_Double_Window;

public:
    void resize_callback(void(*cb)(int, int, int, int, void*), void *arg) {
        resize_cb = cb;
        resize_cb_arg = arg;
    }
    void resize(int X, int Y, int W, int H) override {
        Fl_Double_Window::resize(X, Y, W, H);
        if (resize_cb) resize_cb(X, Y, W, H, resize_cb_arg);
    }

private:
    void(*resize_cb)(int, int, int, int, void*) = nullptr;
    void *resize_cb_arg = nullptr;
};

class DataTable: public Fl_Table_Row {
private:
    inline void draw_header(const char *s, int COL, int X, int Y, int W, int H) {
        fl_push_clip(X, Y, W, H);
        fl_draw_box(FL_GLEAM_THIN_UP_BOX, X, Y, W, H, row_header_color());
        fl_color(FL_FOREGROUND_COLOR);
        fl_draw(s, X + 2, Y, W - 4, H, col_aligns_[COL]);
        fl_pop_clip();
    }

    inline void draw_data(const char *s, int COL, int X, int Y, int W, int H, int selected) {
        fl_push_clip(X, Y, W, H);
        fl_color(selected ? FL_SELECTION_COLOR : FL_BACKGROUND2_COLOR); fl_rectf(X, Y, W, H);
        fl_color(selected ? FL_BACKGROUND2_COLOR : FL_FOREGROUND_COLOR); fl_draw(s, X + 2, Y, W - 4, H, col_aligns_[COL]);
        fl_pop_clip();
    }

protected:
    void draw_cell(TableContext context, int ROW = 0, int COL = 0, int X = 0, int Y = 0, int W = 0, int H = 0) override {
        static char s[40];

        switch (context) {
        case CONTEXT_STARTPAGE:
            fl_font(FL_HELVETICA, FL_NORMAL_SIZE);
            return;
        case CONTEXT_COL_HEADER:
            draw_header(headers_[COL].c_str(), COL, X, Y, W, H);
            return;
        case CONTEXT_CELL:
            sprintf(s, "%d", ROW);;
            draw_data(data_[ROW][COL].c_str(), COL, X, Y, W, H, row_selected(ROW));
            return;
        default:
            return;
        }
    }

public:
    DataTable(int X, int Y, int W, int H, const char *L = 0): Fl_Table_Row(X, Y, W, H, L) {
        row_header(0);
        row_resize(0);

        col_header(1);
        col_resize(0);
        end();
    }

    void rows(int val) override {
        int oldrows = Fl_Table_Row::rows();
        Fl_Table_Row::rows(val);
        if (val != oldrows) {
            data_.resize(val);
            auto cl = Fl_Table_Row::cols();
            for (auto &d: data_)
                d.resize(cl);
        }
    }

    void cols(int val) override {
        int oldcols = Fl_Table_Row::cols();
        Fl_Table_Row::cols(val);
        if (val != oldcols) {
            headers_.resize(val);
            col_aligns_.resize(val);
            for (auto &p: data_)
                p.resize(val);
        }
    }

    inline const std::string &header(int COL) {
        return headers_[COL];
    }
    inline void header(int COL, const std::string &str) {
        headers_[COL] = str;
    }
    void add_row() {
        data_.resize(data_.size() + 1);
        data_.back().resize(Fl_Table_Row::cols());
    }
    void add_row(const std::vector<std::string> &list) {
        int sz = (int)list.size(), rcols = Fl_Table_Row::cols();
        data_.resize(data_.size() + 1);
        if (sz < rcols) {
            auto &vec = data_.back();
            vec.resize(rcols);
            for (int i = 0; i < sz; ++i) {
                vec[i] = list[i];
            }
        } else {
            if (sz > rcols)
                sz = rcols;
            data_.back().assign(list.begin(), list.begin() + sz);
        }
    }
    inline const std::string &data(int ROW, int COL) {
        return data_[ROW][COL];
    }
    inline void data(int ROW, int COL, const std::string &str) {
        data_[ROW][COL] = str;
    }
    inline Fl_Align col_align(int COL) {
        return col_aligns_[COL];
    }
    inline void col_align(int COL, Fl_Align A) {
        col_aligns_[COL] = A;
    }

private:
    std::vector<std::string> headers_;
    std::vector<Fl_Align> col_aligns_;
    std::vector<std::vector<std::string>> data_;
};

class GameTable: public DataTable {
public:
    GameTable(int X, int Y, int W, int H, const char *L = 0): DataTable(X, Y, W, H, L) {
        type(Fl_Table_Row::SELECT_MULTI);
        cols(3);
        col_width(0, 100);
        col_width(2, 50);
        col_align(1, FL_ALIGN_LEFT);
        header(0, "TitleID");
        header(1, " Name");
        header(2, "Region");
    }

    void resized() {
        col_width(1, tiw - 100 - 50);
    }
};

struct GuiContext {
    Downloader dl;
    DataCache cache;
    MainWindow *window;
    Fl_Check_Button *filters[3];
    GameTable *objlist;
};

Gui::Gui(): cont_(new GuiContext) {
    // cont_->dl.setProxy("socks5://127.0.0.1:8888");
}

Gui::~Gui() {
    delete cont_->window;
    delete cont_;
}

void tick_callback(void *arg) {
    ((Gui*)arg)->tick();
    Fl::repeat_timeout(0.05, tick_callback, arg);
}

int Gui::run() {
    Fl::scheme("gleam");
    cont_->window = new MainWindow(500, 400, "Free PS Store");
    cont_->objlist = new GameTable(0, 0, 400, 400);
    auto *grp = new Fl_Group(400, 0, 100, 60);
    cont_->filters[0] = new Fl_Check_Button(400, 0, 100, 20, "All");
    cont_->filters[0]->clear_visible_focus();
    cont_->filters[1] = new Fl_Check_Button(400, 20, 100, 20, "Games");
    cont_->filters[1]->clear_visible_focus();
    cont_->filters[2] = new Fl_Check_Button(400, 40, 100, 20, "DLCs");
    cont_->filters[2]->clear_visible_focus();
    grp->resizable(0);
    grp->end();
    
    cont_->window->resize_callback([](int x, int y, int w, int h, void *arg) {
        GuiContext *cont = (GuiContext*)arg;
        cont->objlist->resized();
    }, cont_);
    cont_->window->size_range(300, 200);
    cont_->window->resizable(cont_->objlist);
    cont_->window->end();
    cont_->window->show(0, nullptr);
    Fl::add_timeout(0.05, tick_callback, this);
    uint64_t deadline = ::time(nullptr) - 86400u;
    for (size_t type = 0; type < CacheTypeMax; ++type) {
        if (cont_->cache.lastUpdate((CacheType)type) < deadline) {
            if (cont_->cache.cacheTime((CacheType)type) == 0) {
                doDownloadCache(type);
            } else {
                auto *dt = cont_->dl.addUrlHead("http://nopaystation.com/tsv/PSV_GAMES.tsv");
                Downloader::setUserData(dt, this);
                Downloader::setUserParam(dt, type);
                Downloader::setFinishCallback(dt, [](DownloadTask *dt) {
                    Gui *gui = (Gui*)Downloader::getUserData(dt);
                    size_t type = Downloader::getUserParam(dt);
                    uint64_t cacheTime = gui->cont_->cache.cacheTime((CacheType)type);
                    Downloader::DownloadInfo di;
                    Downloader::getInfo(dt, di);
                    if (di.filetime != cacheTime) {
                        gui->doDownloadCache(type);
                    }
                    printf("finished header\n");
                });
            }
        }
    }
    updatePkgList();
    return Fl::run();
}

void Gui::tick() {
    cont_->dl.process();
}

void Gui::doDownloadCache(size_t type) {
    const char *urls[]{
        "http://nopaystation.com/tsv/PSV_GAMES.tsv",
        "http://nopaystation.com/tsv/PSV_DLCS.tsv",
        "http://nopaystation.com/tsv/PSV_THEMES.tsv",
    };
    auto *dt = cont_->dl.addUrl(urls[type]);
    Downloader::setUserData(dt, this);
    Downloader::setUserParam(dt, type);
    Downloader::setFinishCallback(dt, [](DownloadTask *dt) {
        Gui *gui = (Gui*)Downloader::getUserData(dt);
        std::string cont;
        if (Downloader::getContent(dt, cont)) {
            Downloader::DownloadInfo di;
            Downloader::getInfo(dt, di);
            gui->cont_->cache.loadCache((CacheType)Downloader::getUserParam(dt), cont, di.filetime);
            gui->updatePkgList();
        }
    });
    Downloader::setProgressCallback(dt, [](DownloadTask*, uint64_t curr, uint64_t total) {
        //printf("%llu/%llu\n", curr, total);
    });
}

void Gui::updatePkgList() {
    std::vector<PkgInfo> pkglist;
    cont_->cache.getPkgList(PSVGame, pkglist);
    cont_->objlist->rows(pkglist.size());
    for (size_t i = 0; i < pkglist.size(); ++i) {
        cont_->objlist->data(i, 0, pkglist[i].titleId);
        cont_->objlist->data(i, 1, pkglist[i].name);
        cont_->objlist->data(i, 2, DataCache::regionToString(pkglist[i].region));
    }
    cont_->objlist->resized();
}
